
Alkaline.TreeBuilder = function(compileResult, TOKEN) {
	this.compileResult = compileResult;
	this.treeAdaptor = this.compileResult.treeAdaptor;
	this.TOKEN = TOKEN;
	this._makeLists();
}

Alkaline.TreeBuilder.init = function() {
	var proto = Alkaline.TreeBuilder.prototype;
	var names = Alkaline.JSInfo.tokenNames;
	var toId = Alkaline.JSInfo.TOKEN;
	for (var i = 0; i < names.length; i++)
		if (proto[names[i]])
			proto[toId[names[i]]] = proto[names[i]];
	Alkaline.TreeBuilder.init = null;
}
Alkaline.TreeBuilder.prototype._makeLists = function() {
	this.controlStructures = [this.TOKEN.For, this.TOKEN.While, this.TOKEN.Do, this.TOKEN.If];
}

Alkaline.TreeBuilder.prototype.getTreePath = function(items) {
	var item = this.Id(items[0]);
	for (var i = 1; i < items.length; i++)
		item = this.SELECT(item, items[i]);
	return item;
}

Alkaline.TreeBuilder.prototype.isControlStructure = function(tree) {
	return this.controlStructures.contains(tree.token.type);
}

Alkaline.TreeBuilder.isSubtree = function(child, parent, breakOnFunction) {
	while (child) {
		if (child === parent)
			return true;
		if (child.token.type === Alkaline.JSInfo.TOKEN.Function)
			break;
		child = child.parent;
	}
	return false;
}

Alkaline.TreeBuilder.prototype.replaceTree = function(oldTree, newTree) {
	var parent = oldTree.parent;
	newTree.coercedTo = oldTree.coercedTo;
	newTree.coercedBy = oldTree.coercedBy;
	parent.setChild(oldTree.childIndex, newTree);
	
	var t = oldTree;
	while (t && t !== newTree)
		t = t.parent;
	var insideNewTree = t === newTree;
	
	if (!insideNewTree)
		this._treeWasRemoved(oldTree);
	
	if (parent.analyzed && !newTree.analyzed)
		this.compileResult.modules.byName.Analyzer.run(newTree);
	return newTree;
}

Alkaline.TreeBuilder.prototype.replaceChildren = function(parent, startIndex, stopIndex, newTree) {
	var oldChildren = parent.children.slice(startIndex, stopIndex+1);
	parent.replaceChildren(startIndex, stopIndex, newTree);
	
	for (var i = 0; i < oldChildren.length; i++) {
		var t = oldChildren[i];
		while (t && t !== newTree)
			t = t.parent;
		var insideNewTree = t === newTree;
		
		if (!insideNewTree)
			this._treeWasRemoved(oldChildren[i]);
	}
	
	if (parent.analyzed && !newTree.analyzed)
		this.compileResult.modules.byName.Analyzer.run(newTree);
	return newTree;
}

Alkaline.TreeBuilder.prototype.setChild = function(parent, index, newTree) {
	var oldTree = parent.children && parent.children[index];
	newTree.coercedTo = oldTree.coercedTo;
	newTree.coercedBy = oldTree.coercedBy;
	parent.setChild(index, newTree);
	
	var t = oldTree;
	while (t && t !== newTree)
		t = t.parent;
	var insideNewTree = t === newTree;
	
	if (!insideNewTree)
		this._treeWasRemoved(oldTree);
	if (parent.analyzed && !newTree.analyzed)
		this.compileResult.modules.byName.Analyzer.run(newTree);
	return newTree;
}

Alkaline.TreeBuilder.prototype.removeTree = function(tree) {
	var parent = tree.parent;
	this._treeWasRemoved(tree);
	if (Alkaline.JSInfo.nonEmptyTokens.contains(parent.token.type) && parent.children.length === 1)
		return this.removeTree(parent);
	parent.deleteChild(tree.childIndex);
	return tree;
}

Alkaline.TreeBuilder.prototype._treeWasRemoved = function(tree) {
	tree.parent = null;
	var list = [ tree ];
	for (var i = 0; i < list.length; i++) {
		tree = list[i];
		tree.analyzed = false;
		if (tree.objectValue)
			if (tree.objectValue && tree.objectValue.declarationToken === tree)
				tree.objectValue.declarationToken = null;
			else if (tree.objectValue.referencedFrom) {
				var idx = tree.objectValue.referencedFrom.indexOf(tree);
				if (idx == tree.objectValue.referencedFrom.length-1)
					tree.objectValue.referencedFrom.pop();
				else
					tree.objectValue.referencedFrom.splice(idx, 1);
				tree.objectValue = null;
			}
		else if (tree.targetInstruction)
			delete tree.targetInstruction;
		if (tree.children && tree.children.length)
			for (var i = 0; i < tree.children.length; i++)
				if (tree.children[i].parent === tree)
					list.push(tree.children[i]);
	}
}

Alkaline.TreeBuilder.prototype.addChild = function(parent, child, index) {
	if (index == null || !parent.children || index >= parent.children.length)
		parent.addChild(child);
	else
		parent.replaceChildren(index, index-1, child);
	if (parent.analyzed && !child.analyzed)
		this.compileResult.modules.byName.Analyzer.run(child);
	return child;
}

Alkaline.TreeBuilder.prototype.moveTreeToBeforeEndOfParent = function(tree) {
	var parent = tree.parent;
	parent.deleteChild(tree.childIndex);
	parent.addChild(tree);
}

Alkaline.TreeBuilder.prototype.treesMatch = function(tree1, tree2) {
	var trees1 = [ tree1 ];
	var trees2 = [ tree2 ];
	for (var idx = 0; idx < trees1.length; idx++) {
		tree1 = trees1[idx]; tree2 = trees2[idx];
		if ((tree1.token.type !== tree2.token.type) || (tree1.token.text !== tree2.token.text))
			return false;
		var c1 = tree1.children ? tree1.children.length : 0;
		var c2 = tree2.children ? tree2.children.length : 0;
		if (c1 !== c2)
			return false;
		if (c1) {
			trees1.addAll(tree1.children);
			trees2.addAll(tree2.children);
		}
	}
	return true;
}

Alkaline.TreeBuilder.prototype.tokensMatch = function(tree1, tree2) {
	if ((tree1.token.type !== tree2.token.type) || (tree1.token.text !== tree2.token.text))
		return false;
	var c1 = tree1.children ? tree1.children.length : 0;
	var c2 = tree2.children ? tree2.children.length : 0;
	if (c1 !== c2)
		return false;
	return true;
}

Alkaline.TreeBuilder.prototype.nil = function() {
	var nil = this.treeAdaptor.nil();
	nil.children = nil.createChildrenList();	// ANTLR Runtime creates list of children on-demand and later expects it to always be present
	return nil;
}

//
//	Node builders
//

Alkaline.TreeBuilder.prototype.ARRAY = function(items) {
	var ARRAY = this.treeAdaptor.create(this.TOKEN.ARRAY, "ARRAY");
	for (var i = 0; i < items.length; i++)
		ARRAY.addChild(items[i]);
	return ARRAY;
}

Alkaline.TreeBuilder.prototype.BLOCK = function(statements) {
	var BLOCK = this.treeAdaptor.create(this.TOKEN.BLOCK, "BLOCK");
	if (statements)
		for (var i = 0; i < statements.length; i++)
			BLOCK.addChild(statements[i]);
	return BLOCK;
}

Alkaline.TreeBuilder.prototype.CALL = function(functionReference, args) {
	var CALL = this.treeAdaptor.create(this.TOKEN.CALL, "CALL");
	CALL.addChild(functionReference);
	if (args instanceof Array)
		for (var i = 0; i < args.length; i++)
			CALL.addChild(args[i]);
	else
		for (var i = 1; i < arguments.length; i++)
			CALL.addChild(arguments[i]);
	return CALL;
}

Alkaline.TreeBuilder.prototype.Catch = function(objectName, block, condition) {
	var Catch = this.treeAdaptor.create(this.TOKEN.Catch, "catch");
	Catch.addChild(this.Id(objectName));
	if (condition)
		Catch.addChild(condition);
	Catch.addChild(block);
	return Catch;
}

Alkaline.TreeBuilder.prototype.Comment = function(text) {
	var comment = this.treeAdaptor.create(this.TOKEN.Comment, text);
	return comment;
}

Alkaline.TreeBuilder.prototype.DefaultDirective = function(className, property, what) {
	var Default = this.treeAdaptor.create(this.TOKEN.DefaultDirective, "default");
	Default.addChild(className);
	Default.addChild(property);
	Default.addChild(what);
	return Default;
}

Alkaline.TreeBuilder.prototype.EXPR = function(op) {
	var EXPR = this.treeAdaptor.create(this.TOKEN.EXPR, "EXPR");
	var children = [];
	if (op)
		if (op instanceof Array)
			children.addAll(op);
		else
			children.push(op);
	for (var i = 0; i < children.length; i++)
		if (children[i].token.type === this.TOKEN.EXPR) {
			children = children[i].children.concat(children.slice(i+1));
			i = -1;	// will be incremented at the next step
		} else if (children[i].token.type !== this.TOKEN.EMPTY)
			EXPR.addChild(children[i]);
	return EXPR;
}

Alkaline.TreeBuilder.prototype.False = function() {
	return this.treeAdaptor.create(this.TOKEN.False, "false");
}

Alkaline.TreeBuilder.prototype.Finally = function(block) {
	var Finally = this.treeAdaptor.create(this.TOKEN.Finally, "finally");
	Finally.addChild(block);
	return Finally;
}

Alkaline.TreeBuilder.prototype.Function = function(name, args, body) {
	var func = this.treeAdaptor.create(this.TOKEN.Function, "function"), argDecl;
	if (name) func.addChild(name);
	func.addChild(argDecl = this.treeAdaptor.create(this.TOKEN.ArgDecl, "ArgDecl"));
	for (var i = 0; i < args.length; i++)
		argDecl.addChild(args[i]);
	func.addChild(body);
	return func;
}

Alkaline.TreeBuilder.prototype.Id = function(name) {
	return this.treeAdaptor.create(this.TOKEN.Id, String(name));
}

Alkaline.TreeBuilder.prototype.If = function(condition, trueBranch, falseBranch) {
	var If = this.treeAdaptor.create(this.TOKEN.If, "if");
	If.addChild(condition);
	If.addChild(trueBranch);
	if (falseBranch)
		If.addChild(falseBranch);
	return If;
}

Alkaline.TreeBuilder.prototype.Null = function() {
	return this.treeAdaptor.create(this.TOKEN.Null, "null");
}

Alkaline.TreeBuilder.prototype.Number = function(value) {
	return this.treeAdaptor.create(this.TOKEN.Number, Alkaline.JSInfo.shortestNumberRepresentation(String(value)));
}

Alkaline.TreeBuilder.prototype.Operator = function(op, lhs, rhs) {
	var Operator = this.treeAdaptor.create(this.TOKEN.Operator, op);
	Operator.addChild(lhs);
	Operator.addChild(rhs);
	return Operator;
}

Alkaline.TreeBuilder.prototype.QMark = function(condition, trueBranch, falseBranch) {
	var Operator = this.treeAdaptor.create(this.TOKEN.Operator, "?");
	Operator.addChild(condition);
	Operator.addChild(trueBranch);
	Operator.addChild(falseBranch);
	return Operator;
}

Alkaline.TreeBuilder.prototype.Return = function(what) {
	var Return = this.treeAdaptor.create(this.TOKEN.Return, "return");
	if (what)
		Return.addChild(what);
	return Return;
}

Alkaline.TreeBuilder.prototype.SELECT = function(objectReference, propertyName) {
	var SELECT = this.treeAdaptor.create(this.TOKEN.SELECT, "SELECT");
	SELECT.addChild(objectReference);
	if (!propertyName.token)
		propertyName = this.String(propertyName);
	SELECT.addChild(propertyName);
	return SELECT;
}

Alkaline.TreeBuilder.prototype.String = function(str) {
	var charMap = Alkaline.TreeBuilder.prototype.String.charMap;
	str = str.replace(/\\/g, '\\\\').replace(
		/[\u0000-\u001F\u007F-\u009F\u200E\u200F\u202A-\u202E\uFFF9-\uFFFF]/g,
		function(chr) {
			if (!charMap[chr]) {
				var nr = chr.charCodeAt(0).toString(16);
				while (nr.length < 4) nr = '0' + nr;
				charMap[chr] = '\\u' + nr;
			}
			return charMap[chr];
		});
	str = '"' + str.replace(/"/g, '\\"') + '"';
	return this.treeAdaptor.create(this.TOKEN.String, str);
}
Alkaline.TreeBuilder.prototype.String.charMap = {};

Alkaline.TreeBuilder.prototype.Throw = function(what) {
	var Throw = this.treeAdaptor.create(this.TOKEN.Throw, "throw");
	Throw.addChild(what);
	return Throw;
}

Alkaline.TreeBuilder.prototype.True = function() {
	return this.treeAdaptor.create(this.TOKEN.True, "true");
}

Alkaline.TreeBuilder.prototype.Try = function() {
	var Try = this.treeAdaptor.create(this.TOKEN.Try, "try");
	for (var i = 0; i < arguments.length; i++)
		Try.addChild(arguments[i]);
	return Try;
}

Alkaline.TreeBuilder.prototype.UnaryOperator = function(op, arg) {
	var tree = this.treeAdaptor.create(this.TOKEN.UnaryOperator, op);
	tree.addChild(arg);
	return tree;
}

Alkaline.TreeBuilder.prototype.Var = function(names) {
	var Var = this.treeAdaptor.create(this.TOKEN.Var, "var");
	if (names)
		for (var i = 0; i < names.length; i++)
			Var.addChild(this.Id(names[i]));
	return Var;
}
